package impl.ui;

import interfaces.MapRobots;
import interfaces.RobotInfo;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JLabel;

import classes.Constants;
import classes.Coordinate;
import classes.ObservableList;
import classes.Utils;

public class MapView extends JComponent implements MouseListener {
	private final String TAG = Constants.TAG + "." + getClass().getSimpleName();

	private static final long serialVersionUID = -1903490141143226370L;

	private int rows = 0;

	private int cols = 0;
	
	private double tileWidth = 0.0;
	
	private double tileHeight = 0.0;

	private MapTile[][] tiles = null;
	
	private ObservableList<Coordinate> freeTiles = new ObservableList<Coordinate>();

	private ObservableList<Coordinate> candyTiles = new ObservableList<Coordinate>();

	private ObservableList<RobotInfo> robotTiles = new ObservableList<RobotInfo>();
	
	/** Scheduler for hiding the status label */
	private final ScheduledExecutorService worker = Executors.newSingleThreadScheduledExecutor();
	
	/** Future scheduled task */
	private ScheduledFuture<?> scheduledFuture;
	
	private JLabel robotStatus = new JLabel();

	private Graphics graphics;
	
	public MapView(int width, int height) {
		Utils.logC(TAG, "Creating MapView");

		// Draw border around the view
		setBorder(BorderFactory.createLineBorder(Color.GRAY, 2));
		
		setLayout(null);

		// Label for robot status (on click)
		robotStatus = new JLabel("Test");
		robotStatus.setLocation(0, 0);
		robotStatus.setSize(100, 20);
		robotStatus.setBackground(Color.BLUE);
		robotStatus.setVisible(false);
		robotStatus.setBorder(BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.RAISED));
		add(robotStatus);

		this.rows = height;
		this.cols = width;
		
		addMouseListener(this);
	}

	@Override
	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		graphics = g;

		if (tiles == null)
			initTiles();

		synchronized (freeTiles) {
			setTiles();
		}

		g.setColor(new Color(Constants.MAIN_BGR_COLOR));
		g.fillRect(0, 0, getWidth(), getHeight());

		Graphics2D g2 = (Graphics2D) g;
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

		for (int i = 0; i < rows; i++)
			for (int j = 0; j < cols; j++)
				tiles[i][j].draw(g2);
	}

	private void initTiles() {
		// Initialize map tiles with default state
		Utils.logC(TAG, "Map size: " + getWidth() + " | " + getHeight());

		tileWidth = (double) (getWidth() - 2 * MapTile.PADDING) / cols;
		tileHeight = (double) (getHeight() - 2 * MapTile.PADDING) / rows;

		Utils.logC(TAG, "Tile size: " + tileWidth + " | " + tileHeight);

		tiles = new MapTile[rows][cols];
		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < cols; j++) {
				double x = MapTile.PADDING + j * tileWidth;
				double y = MapTile.PADDING + i * tileHeight;
				Rectangle2D.Double r = new Rectangle2D.Double(x, y, tileWidth, tileHeight);
				tiles[i][j] = new MapTile(r, new Coordinate(j, i), MapTile.STATE_DEFAULT);
			}
		}
	}

	public void setViews(ObservableList<Coordinate> freePositions, ObservableList<Coordinate> candiesPosition, MapRobots robotsPosition) {
		synchronized (freeTiles) {
			this.freeTiles = freePositions;
			this.candyTiles = candiesPosition;
			this.robotTiles = robotsPosition.getRobotsInfo();

			repaint();
		}
	}

	private void setTiles() {
		// Make all tiles obstacles
		for (int i = 0; i < rows; i++)
			for (int j = 0; j < cols; j++) {
				tiles[i][j].setState(MapTile.STATE_OBSTACLE);
			}
				

		// Mark the free ones
		for (int i = 0; i < rows; i++)
			for (int j = 0; j < cols; j++) {
				Iterator<Coordinate> posItr = freeTiles.iterator();
				while(posItr.hasNext()) {
					Coordinate pos = posItr.next();
					if (pos.equals(tiles[i][j].getPosition()))
						tiles[i][j].setState(MapTile.STATE_DEFAULT);
				}
			}
		// Mark the candies
		for (int i = 0; i < rows; i++)
			for (int j = 0; j < cols; j++) {
			Iterator<Coordinate> posItr = candyTiles.iterator();
			while(posItr.hasNext()) {
				Coordinate pos = posItr.next();
				if (pos.equals(tiles[i][j].getPosition()))
					tiles[i][j].setState(MapTile.STATE_CANDY);
			}
		}

		// Mark the robots
		for (int i = 0; i < rows; i++)
			for (int j = 0; j < cols; j++) {
			Iterator<RobotInfo> posItr = robotTiles.iterator();
			while(posItr.hasNext()) {
				RobotInfo pos = posItr.next();
				if (pos.getPosition().equals(tiles[i][j].getPosition()))
					if (pos.hasCandy())
						tiles[i][j].setState(MapTile.STATE_ROBOT_WITH_CANDY);
					else
						tiles[i][j].setState(MapTile.STATE_ROBOT);
			}
		}
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		Point click = e.getPoint();
		int x = (int) (click.x / tileWidth);
		int y = (int) (click.y / tileHeight);
		
		if (x < 0 || x >= cols || y < 0 || y >= rows)
			return;
		
		Utils.logC(TAG, "Mouse click: " + x +  " (" + click.x + ") | " + y + " (" + click.y + ")");
		
		// Check if robot was clicked
		robotClicked(x, y);
		
		// Check if obstacle was clicked
		obstacleClicked(x, y);
	}
	
	private void robotClicked(int x, int y) {
		ListIterator<RobotInfo> itr = robotTiles.listIterator();
		while (itr.hasNext()) {
			RobotInfo info = itr.next();
			if (info.getPosition().getX() == x && info.getPosition().getY() == y) {
				// Remove any previous works from scheduler
				if (scheduledFuture != null)
					scheduledFuture.cancel(true);
				
				String state_text = "";
				int state_value = info.getState();
				if (state_value == 0)
					state_text = "THINKING";
				else if (state_value == 1)
					state_text = "DECIDING";
				else if (state_value == 2)
					state_text = "ACTING";
				else if (state_value == 3)
					state_text = "DEAD";
				else
					state_text = "UNKNOWN";
				
				String statusText = "Robot (" + x + ", " + y + ") state: " + state_text + (info.hasCandy() ? " with candy" : " without candy") ;
				Utils.logC(TAG, statusText);
				
				// Show label with statuses
				FontMetrics metrics = graphics.getFontMetrics(robotStatus.getFont());
				int hgt = metrics.getHeight();
				int adv = metrics.stringWidth(statusText);
				Dimension size = new Dimension(adv+7, hgt+4);
				robotStatus.setText(statusText);
				robotStatus.setSize(size);
				robotStatus.setLocation(0, 0);
				robotStatus.setVisible(true);
				
				// Hide status after 5 seconds
				scheduledFuture = worker.schedule(new Runnable() {
					@Override
					public void run() {
						robotStatus.setVisible(false);
					}
				}, 5, TimeUnit.SECONDS);
			}
		}
	}
	
	private void obstacleClicked(int x, int y) {
		for (int i = 0; i < rows; i++)
			for (int j = 0; j < cols; j++) {
				if (tiles[i][j].getPosition().getX() == x && tiles[i][j].getPosition().getY() == y) {
					if (tiles[i][j].getState() == MapTile.STATE_OBSTACLE) {
						// Remove one of the other lines
						for (int k = 0; k < rows; k++) {
							if (tiles[k][j].getState() != MapTile.STATE_OBSTACLE) {
								for (int l = cols / 4; l < cols - cols / 4; l++)
									freeTiles.remove(new Coordinate(l, k));
								break;
							}
						}
						
						// Add obstacle line to free places
						synchronized (tiles) {
							int line = tiles[i][j].getPosition().getY();
							for (int k = cols / 4; k < cols - cols / 4; k++)
								freeTiles.add(new Coordinate(k, line));
						}
					}
				}
			}
	}

	@Override
	public void mouseEntered(MouseEvent e) {}

	@Override
	public void mouseExited(MouseEvent e) {}

	@Override
	public void mousePressed(MouseEvent e) {}

	@Override
	public void mouseReleased(MouseEvent e) {}
}
